/*
 * Copyright (c) 2003, the JUNG Project and the Regents of the University
 * of California
 * All rights reserved.
 *
 * This software is open-source under the BSD license; see either
 * "license.txt" or
 * http://jung.sourceforge.net/license.txt for a description.
 */
package cadtoolbox.graphical;

import edu.uci.ics.jung.algorithms.layout.AbstractLayout;
import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.RadiusGraphElementAccessor;
import edu.uci.ics.jung.algorithms.layout.util.RandomLocationTransformer;
import edu.uci.ics.jung.algorithms.util.IterativeContext;
import edu.uci.ics.jung.graph.Graph;

//import edu.uci.ics.jung.graph.AbstractGraph;
//import edu.uci.ics.jung.graph.util.EdgeType;
//import edu.uci.ics.jung.graph.util.Pair;
//import edu.uci.ics.jung.algorithms.layout.Layout;
//import edu.uci.ics.jung.visualization.BasicVisualizationServer;
//import edu.uci.ics.jung.visualization.VisualizationViewer;
//import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
//import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
//import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
//import edu.uci.ics.jung.visualization.control.ScalingControl;
//import edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position;

import cadtoolbox.model.OligoGraph;

import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.map.LazyMap;

import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Implements a self-organizing map layout algorithm, based on Meyer's
 * self-organizing graph methods.
 * 
 * @author Yan Biao Boey
 */
public class OligoISOMLayout<V, E> extends AbstractLayout<V, E> implements
		IterativeContext {


	private int maxEpoch;
	private int epoch;
	private double adaption;
	private double initialAdaption;
	private double minAdaption;
	private String status = null;
	
	
	//Electric Spring////////
	private int progress = 0;
	private int l = 0;
	private int l2 = 0;
	private int P = 0;
	private int S = 500;
	private ArrayList<Double> x0 = new ArrayList<Double>();
	private ArrayList<Double> x1 = new ArrayList<Double>();
	private ArrayList<Double> y0 = new ArrayList<Double>();
	private ArrayList<Double> y1 = new ArrayList<Double>();
	private ArrayList<Double> Fx = new ArrayList<Double>();
	private ArrayList<Double> Fy = new ArrayList<Double>();
	private double E = 999999999;
	private double E0 = 0;
	private double Emin = 999999999;
	private double step = 60;	//step for coordinates update
	private boolean Conv = false;
	private int Deg = 0;
	private double K = 0;
	private double Kr = 0;
	private int Try = 1;
	private double tolerance = 0.9;
	private int maxRetrys = 5;
	private int Retrys = 0;
	private double toleranceUpdate = 0.9;
	///////////////////////////
	

	/**
	 * Returns the current number of epochs and execution status, as a string.
	 */
	public String getStatus() {
		return status;
	}

	/**
	 * Creates an <code>ISOMLayout</code> instance for the specified graph
	 * <code>g</code>.
	 * 
	 * @param g
	 */
	public OligoISOMLayout(OligoGraph<V, E> g) {
		super(g);
	}

	public void initialize() {
		setInitializer(new RandomLocationTransformer<V>(new Dimension(
				(int) Math.max(180, getSize().getWidth()), (int) Math.max(180,
						getSize().getHeight()))));
		maxEpoch = 2000;
		epoch = 1;
		initialAdaption = 90.0D / 100.0D;
		adaption = initialAdaption;
		minAdaption = 0;
	
	}
	
	
	/**
	 * Advances the current positions of the graph elements.
	 */
	public void step() {
		status = "epoch: " + epoch + "; ";
		if (epoch < maxEpoch) {
			status += " status: running";
		
			if(!Conv)
				optimizeGraph();
			if(Conv){
				epoch = maxEpoch;
				x0 = null;
				x1 = null;
				y0 = null;
				y1 = null;
				Fx = null;
				Fy = null;
			}
			
		} else {
			status += "adaption: " + adaption + "; ";
			status += "status: done";
			// done = true;
		}
	}

	/**
	 * This one is an incremental visualization.
	 * 
	 * @return <code>true</code> is the layout algorithm is incremental,
	 *         <code>false</code> otherwise
	 */
	public boolean isIncremental() {
		return true;
	}

	/**
	 * Returns <code>true</code> if the vertex positions are no longer being
	 * updated. Currently <code>ISOMLayout</code> stops updating vertex
	 * positions after a certain number of iterations have taken place.
	 * 
	 * @return <code>true</code> if the vertex position updates have stopped,
	 *         <code>false</code> otherwise
	 */
	public boolean done() {
		return epoch >= maxEpoch;
	}

	/**
	 * Resets the layout iteration count to 0, which allows the layout algorithm
	 * to continue updating vertex positions.
	 */
	public void reset() {
		epoch = 0;
	}


	
	public void energyComputing(){
			
		double C = 2;		//regulates the relative strength of the attractive and repulsive forces
		double p = 0;		//the larger p, the weaker the long range repulsive forces
		double q = 0;		//the larger q, the stronger attractive forces
		double tol = 0.5;		//tolerance parameter on convergence
		//epoch++;
		
		//reset energy
		E0 = E;
		E = 0;
		l++;
		
		//System.err.println("Iteration " + (l));
		if(l>0){
		
		int i = 0;
		for (V v1 : getGraph().getVertices()) {
			
			//coordinates variables
			Fx.set(i, 0.0);
			Fy.set(i, 0.0);
			x0.set(i, x1.get(i));
			y0.set(i, y1.get(i));
			x1.set(i, getX(v1));
			y1.set(i, getY(v1));
			
			//Deal with inhibition sequences
			String VertName = v1.toString();
			V FirstPart = null;
			V SecondPart = null;
			if(((OligoGraph<V,E>) graph).isInhibitor(v1)){
//				String VertNameI = VertName.substring(1);
//				int L = VertNameI.length();
//				int e = 1;
//				int e1 = 1;
//				//System.err.println("Inhibiting vertex " + (i+1) + " :     I" + VertNameI + "    L = " + L);
//				while(e < L){
//					for(V v2 : getGraph().getVertices()){
//						String Vert2Name = v2.toString();
//						if(Vert2Name.equals(VertNameI.substring(L - e))){
//							SecondPart = VertNameI.substring(L - e);
//							e1++;
//						}
//					}
//					//System.err.println(" Second part : " + SecondPart);
//					e++;
//				}
//				FirstPart = VertNameI.substring(0, L - (e1 - 1));
//				//System.err.println(" First part :  " + FirstPart);
//				//System.err.println("Vertex " + v1.toString() + " :      " + VertName + " :  inhibits " + FirstPart + " and " + SecondPart );
			E e = ((OligoGraph<V,E>)graph).getInhibitedEdge(v1);
			FirstPart = ((OligoGraph<V,E>) graph).getEndpoints(e).getFirst();
			SecondPart = ((OligoGraph<V,E>) graph).getEndpoints(e).getSecond();
			}
			
			for (V v2 : getGraph().getVertices()) {
				
				String Vert2Name = v2.toString();
				double x2 = getX(v2);
				double y2 = getY(v2);
				double r = Math.pow( Math.pow( (x2 - x1.get(i)), 2) + Math.pow( (y2 - y1.get(i)), 2), 0.5);
				
				if(v1 != v2){	
					
					// create repulsion force between each vertexes
					double Frx = Math.pow( K / r, 2.5+p ) * (-C) * ( ( x2 - x1.get(i) ) / r );
					double Fry = Math.pow( K / r, 2.5+p ) * (-C) * ( ( y2 - y1.get(i) ) / r );
					Fx.set(i, Fx.get(i) + Frx );
					Fy.set(i, Fy.get(i) + Fry );
					
					//if(!v1.toString().startsWith("C"))
					//System.err.println("Vertex " + v1.toString() + " [" + (int)getX(v1) + "," + (int)getY(v1) + "] " + 
					//		" repulsed by " + v2.toString() + " [" + (int)getX(v2) + "," + (int)getY(v2) + "]          " +
					//		"with force Frx = " + Frx + ", Fry = " + Fry + "   r = " + r);
						
					if( ( getGraph().findEdge(v1,v2) != null || getGraph().findEdge(v2,v1) != null ) || ( v2.equals(FirstPart) || v2.equals(SecondPart) ) ){
						double Fax = Math.pow( r / K, 2+q ) * ( ( x2 - x1.get(i) ) / r );
						double Fay = Math.pow( r / K, 2+q ) * ( ( y2 - y1.get(i) ) / r );
						Fx.set(i, Fx.get(i) + Fax );
						Fy.set(i, Fy.get(i) + Fay );
						
						//if(!v1.toString().startsWith("C"))
						//System.err.println("Vertex " + v1.toString() + " [" + (int)getX(v1) + "," + (int)getY(v1) + "] " + 
						//		" attracted by " + v2.toString() + " [" + (int)getX(v2) + "," + (int)getY(v2) + "]         " +
						//		"with force Fax = " + Fax + ", Fay = " + Fay + "   r = " + r);
					}
				}
			}
		
			//repulsing force of the walls
			double r = getX(v1); 
			double Fm = Math.pow( (K/4) / r, 1 ) * (-C) * ( ( 0 - x1.get(i) ) / r ) /*+ Math.pow( r / (K/2), 1 ) * ( ( 0 - x1.get(i) ) / r )*/;
			r = getSize().getWidth() - getX(v1);
			Fm += Math.pow( (K/4) / r, 1 ) * (-C) * ( ( getSize().getWidth() - x1.get(i) ) / r ) /*+ Math.pow( r / (K/2), 2 ) * ( ( getSize().getWidth() - x1.get(i) ) / r )*/;
			Fx.set(i, Fx.get(i) + Fm );
			r = getY(v1);
			Fm = Math.pow( (K/4) / r, 1 ) * (-C) * ( ( 0 - y1.get(i) ) / r ) /*+ Math.pow( r / (K/2), 1 ) * ( ( 0 - y1.get(i) ) / r )*/;
			r = getSize().getHeight() - getY(v1);
			Fm += Math.pow( (K/4) / r, 1 ) * (-C) * ( ( getSize().getHeight() - y1.get(i) ) / r ) /*+ Math.pow( r / (K/2), 2 ) * ( ( getSize().getHeight() - y1.get(i) ) / r )*/;
			Fy.set(i, Fy.get(i) + Fm );
		
			//Energy computing
			double e = (Fx.get(i) * Fx.get(i)) + (Fy.get(i) * Fy.get(i));
			E += e;
	
			//modify vertex position
			double V = 3.0;		//evolution speed factor
			double X = x1.get(i) + Math.min(V * Abs(Fx.get(i)), step) * (Fx.get(i) / Abs(Fx.get(i)));
			double Y = y1.get(i) + Math.min(V * Abs(Fy.get(i)), step) * (Fy.get(i) / Abs(Fy.get(i)));
			if( X < getSize().getWidth() - 10 && X > 10 )	
				x1.set(i, X);
			if( Y < getSize().getHeight() - 10 && Y > 10 )
				y1.set(i, Y);
			setLocation(v1, x1.get(i), y1.get(i));
			
			i++;
		}
		
		//update step
		step = updateStep(step,E0,E);
		
		//test convergence
		//System.err.println("Resulsts of computation :");
		//System.err.println(" -> E0 = " + (int)E0 + ", E = " + (int)E + ", (K = " + (int)K + ")");
		//System.err.println(" -> Updated step : " + step + " (porgress " + progress + ")");
		Conv = true;
		int n = 0;
		for(V v : getGraph().getVertices()){
			double D = Abs( Math.pow(x1.get(n)-x0.get(n), 2) + Math.pow(y1.get(n)-y0.get(n), 2) );
			if(D >= tol)
				Conv = false;
			n++;
		}
		
		}
		if(l > S){
			l2 += l;
			resetElectricSpring();
			Try++;
			S += S * 1.2;
			//System.err.println("Try " + Try + "...");
		}
		
		if(Conv){
			//System.out.println("Energy minimized in " + l + " iterations (E = " + E +")");
			l2 += l;
		}
		
	}
	
	
	public double updateStep(double step, double E0, double E){
		double t = 0.98;

		if(E < E0) {
			progress ++;
			if(progress > 2) {
				progress = 0;
				step = step / t;
			}
		} else {
			progress = 0;
			step = step * t;
		}

		return step;
	}
	
	
	public double Abs(double x){
		double x1 = 0;
		if(x > 0)
			x1 = x;
		else
			x1 = -x;
		return x1;
	}
	
	
	public int Floor(double x){
		int x1 = (int)x;
		return x1;
	}
	
	
	public int initElectricSpring(){
		
		progress = 0;
		l = 0;
		E = 999999999;
		E0 = 0;
		step = Math.pow( Math.pow(getSize().getHeight()/4, 2) + Math.pow(getSize().getWidth()/4, 2), 0.5);	//step for coordinates update
		
		//initialization coordinates arrays
		int m = 0;
		int Deg = 0;
		for(V v : getGraph().getVertices())
		{
			Fx.add(0.0);
			Fy.add(0.0);
			x0.add(0.0);
			y0.add(0.0);
			if(m == 0){
				x1.add( getSize().getWidth()/2 );
				y1.add( getSize().getHeight()/2 );
			}
			if(m != 0){
				x1.add( (Math.random()*(getSize().getWidth() - getSize().getWidth()/2.5) + getSize().getWidth()/5) );
				y1.add( (Math.random()*(getSize().getHeight() - getSize().getHeight()/2.5) + getSize().getWidth()/5) );
			}
			setLocation(v, x1.get(m), y1.get(m));
			m++;
			Deg += getGraph().degree(v);
		}
		//System.err.println(m + " vertices to optimize, degree = " + Deg );
		
		//determine optimal distance K
		double A = getSize().getHeight() * getSize().getWidth();
		double C = 0.9;
		K = Math.pow(C*C*A/m, 0.5);
		if(K > getSize().getHeight() / 5)
			K = getSize().getHeight() / 5;
		
		//Dertermine min convergence energy
		Emin = m;
		
		return Deg;
	}

	
	public int resetElectricSpring(){
		
		progress = 0;
		E = 999999999;
		E0 = 0;
		step = Math.pow( Math.pow(getSize().getHeight()/4, 2) + Math.pow(getSize().getWidth()/4, 2), 0.5);	//step for coordinates update
		
		//initialization coordinates arrays
		int m = 0;
		int Deg = 0;
		for(V v : getGraph().getVertices())
		{
			Fx.set(m,0.0);
			Fy.set(m,0.0);
			x0.set(m,0.0);
			y0.set(m,0.0);
			if(m == 0){
				x1.set(m, getSize().getWidth()/2 );
				y1.set(m, getSize().getHeight()/2 );
			}
			if(m != 0){
				x1.set(m, (Math.random()*(getSize().getWidth() - getSize().getWidth()/2.5) + getSize().getWidth()/5) );
				y1.set(m, (Math.random()*(getSize().getHeight() - getSize().getHeight()/2.5) + getSize().getWidth()/5) );
			}
			setLocation(v, x1.get(m), y1.get(m));
			m++;
			Deg += getGraph().degree(v);
		}
		//System.err.println(m + " vertices detected, degree = " + Deg );
		
		return Deg;
	}
	
	
	public void centerGraph(){
		double Xm = 0;
		double Ym = 0;
		int n = 0;
		for(V v : getGraph().getVertices()){
			Xm += getX(v);
			Ym += getY(v);
			n++;
		}
		Xm = Xm / n;
		Ym = Ym / n;
		//System.err.println("Xm = " + Xm + ", Ym = " + Ym);
		Xm = getSize().getWidth() / 2 - Xm;
		Ym = getSize().getHeight() / 2 - Ym;
		for(V v : getGraph().getVertices()){
			if(getX(v) < 0.9 * getSize().getWidth() && getX(v) > 0.1 * getSize().getHeight() && getY(v) < 0.9 * getSize().getHeight() && getY(v) > 0.1 * getSize().getHeight())
				setLocation(v, getX(v) + Xm, getY(v) + Ym);
		}
	}
	
	
	public boolean distanceCheck(double tol){
		
		boolean Dist = true;
		for(V v1 : getGraph().getVertices()){
			for(V v2 : getGraph().getVertices()){
				if(v1 != v2){
					double dx = getX(v2) - getX(v1);
					double dy = getY(v2) - getY(v1);
					double r2 = dx * dx + dy * dy;
					if( r2 < K*K*tol ){
						Dist = false;
						Retrys++;
						//System.out.println("D = " + r2 + "  K = " + (K*K*tol) + "   Retrys = " + Retrys);
						if( r2 < Kr )
							Kr = r2;
					}
				}
			}
		}
		return Dist;
	}

	
	public void optimizeGraph(){
		
		S = 100;
		Deg = initElectricSpring();
		Conv = false;
		boolean Dist = false;
		
		while(!Dist){
			while(!Conv){
				energyComputing();
			}
			Kr = 99999999;
			Dist = distanceCheck(tolerance);
			l = 0;
			Try = 1;
			S = 100;
			if(!Dist){
				if(Retrys >= maxRetrys){
					Retrys = 0;
					//System.out.println("Too difficult to draw! update tolerance factor " + tolerance + " -> " + tolerance*toleranceUpdate);
					tolerance = tolerance*toleranceUpdate;
					if(maxRetrys > 1)
						maxRetrys--;
				}
				Conv = false;
				//System.out.println("Nodes are too close! Redrawing.");
				Deg = resetElectricSpring();
			}
		}
		
		//if(l2 > 15000)
			System.out.println("Suitable representation found after " + l2 + " iterations");
		
		centerGraph();
	}
	
	/**
	 * Takes into account the saturation panel that is added at the top
	 */
	@Override
	public Dimension getSize(){
		Dimension size = super.getSize();
		Dimension ret = new Dimension();
		ret.height = size.height-SaturationPanel.sizeY;
		ret.width = size.width;
		return ret;
	}
	
}